# -*-coding:utf-8-*-

import logging, json, motor
from pymongo.collection import ReturnDocument
from utils.misc import generate_random_no, extract_fields
from bson.objectid import ObjectId
import pymongo

Base_Document_Structure = {
    'field': 'v0',

    'object_field': {
        'sub_object_field': 'sv0',
    },

    'array_field': [
        {
            'sub_object_field': {
                # 这其中的字段可以直接通过 mongo 更新(待确认)
            },
            'sub_array_field': [
                # 二级以上数组中的字段只能靠人工逻辑更新，mongo 并不提供原生支持(待确认)
            ]
        },
    ]
}


class BaseRecord(object):
    def __init__(self, data):
        self.data = data

    def __getattr__(self, item):
        return self._get_optional_field(item)

    def _get_optional_field(self, *args):
        field = self.data
        for arg in args:
            if arg in field:
                field = field[arg]
            else:
                return None
        return field

    @property
    def id_str(self):
        return str(self.data['_id'])


class SubRecordTemp(BaseRecord):
    def __getattr__(self, item):
        if 'special_item' == item:
            args = ('spec', 'item')
        else:
            args = (item,)
        return self._get_optional_field(*args)


'''
- 功能 -
更新文档指定字段
更新内嵌文档中指定字段
更新数组字段中指定对象中的字段
'''


class BaseDao(object):
    def __init__(self, db, collection_name):
        self.logger = logging.getLogger()
        self.db = db
        self.collection = self.db.get_collection(collection_name)
        self.requested_fields = []
        self.optional_fields = []
        self.readonly_fields = []

    def _get_record_object(self, data):
        return BaseRecord(data)

    def get_record_by_id(self, rid, rid_field_name='_id'):
        rid_field_value = ObjectId(rid) if '_id' == rid_field_name else rid
        filter_cond = {rid_field_name: rid_field_value}
        data = self.find_one(filter_cond)
        if data is not None:
            return self._get_record_object(data)
        else:
            return None

    @staticmethod
    def extract_fields(src, required_fields=None, optional_fields=None):
        return extract_fields(src, required_fields, optional_fields)

    def insert_one_with_unique_no(self, fields, field_base_name, length):
        field_no = '%s_no' % field_base_name
        field_id = '%s_id' % field_base_name
        while True:
            try:
                fields[field_no] = str(generate_random_no(length))
                r = self.insert_one(fields)
                return {field_id: str(r.inserted_id), field_no: fields[field_no]}
            except:
                self.logger.info('duplicate %s: %s' % (field_no, fields[field_no]))
                # if '_id' in fields:
                #    del fields['_id']

    def get_paging_result(self, filter_cond, projection, id_field_name, **kwargs):
        kwargs = self.extract_fields(kwargs, optional_fields=['skip', 'limit', 'sort'])
        if 'skip' in kwargs:
            kwargs['skip'] = int(kwargs['skip'])
        if 'limit' in kwargs:
            kwargs['limit'] = int(kwargs['limit'])
        kwargs['sort'] = [('_id', pymongo.DESCENDING)]
        result = []
        for r in self.find(filter_cond, projection, **kwargs):
            r[id_field_name] = str(r.pop('_id'))
            result.append(r)
        return result

    def count(self, *args, **kwargs):
        return self.collection.count(*args, **kwargs)

    def find(self, *args, **kwargs):
        return self.collection.find(*args, **kwargs)

    def find_one(self, filter=None, *args, **kwargs):
        return self.collection.find_one(filter, *args, **kwargs)

    def insert_one(self, document, bypass_document_validation=False):
        return self.collection.insert_one(document, bypass_document_validation)

    def insert_many(self, documents, ordered=True,
                    bypass_document_validation=False):
        return self.collection.insert_many(documents, ordered, bypass_document_validation)

    def update_one(self, filter, update, upsert=False,
                   bypass_document_validation=False,
                   collation=None):
        return self.collection.update_one(filter, update, upsert, bypass_document_validation, collation)

    def update_many(self, filter, update, upsert=False,
                    bypass_document_validation=False, collation=None):
        return self.collection.update_many(filter, update, upsert, bypass_document_validation, collation)

    def delete_one(self, filter, collation=None):
        return self.collection.delete_one(filter, collation)

    def delete_many(self, filter, collation=None):
        return self.collection.delete_many(filter, collation)

    def find_one_and_update(self, filter, update,
                            projection=None, sort=None, upsert=False,
                            return_document=ReturnDocument.BEFORE, **kwargs):
        return self.collection.find_one_and_update(filter, update, projection, sort, upsert, return_document, **kwargs)

    def find_one_and_delete(self, filter,
                            projection=None, sort=None, **kwargs):
        return self.collection.find_one_and_delete(filter, projection, sort, **kwargs)

    '''
        object
    '''

    def create_object(self, data, **kwargs):
        obj = self.extract_fields(data, self.requested_fields, self.optional_fields)
        obj = dict(obj, **kwargs)
        result = self.insert_one(obj)
        return str(result.inserted_id)

    def update_object(self, filter_cond, data):
        fields = self.requested_fields + self.optional_fields
        fields = list(set(fields) - set(self.readonly_fields))
        modification = self.extract_fields(data, optional_fields=fields)
        result = self.update_one(filter_cond, {'$set': modification})
        return 1 == result.matched_count

    def delete_object(self, filter_cond):
        result = self.delete_one(filter_cond)
        return 1 == result.deleted_count

    '''
        sub-object
    '''

    def get_object_field(self, filter_cond, field):
        projection = {field: 1, '_id': 0}
        result = self.collection.find_one(filter_cond, projection)
        if result is not None:
            return result[field]
        else:
            return None

    def delete_object_field(self, filter_cond, field):
        result = self.collection.update_one(filter_cond, {'$unset': {field: 0}})
        return 1 == result.matched_count

    '''
        sub-object-field
    '''

    def get_sub_object_field(self, filter_cond, field, sub_field):
        sub_field_name = '%s.%s' % (field, sub_field)
        projection = {sub_field_name: 1, '_id': 0}
        new_filter_cond = dict({sub_field_name: {'$exists': 1}}, **filter_cond)
        result = self.collection.find_one(new_filter_cond, projection)
        if result is not None:
            return result[field][sub_field]
        else:
            return None

    def add_sub_object_field(self):
        pass

    def update_sub_object_field(self, filter_cond, field, sub_field, data):
        sub_field_name = '%s.%s' % (field, sub_field)
        result = self.update_one(filter_cond, {'$set': {sub_field_name: data}})
        return 1 == result.matched_count

    def delete_sub_object_field(self, filter_cond, field, sub_field):
        sub_field_name = '%s.%s' % (field, sub_field)
        result = self.update_one(filter_cond, {'$unset': {sub_field_name: 0}})
        return 1 == result.matched_count

    '''
       sub-array-element
    '''

    def get_sub_array_element(self, filter_cond, field):
        result = self.find_one(filter_cond, {'%s.$' % field: 1})
        return result[field][0]

    def add_sub_array_element(self, filter_cond, field, data):
        result = self.update_one(filter_cond, {'$push': {field: data}})
        return 1 == result.matched_count

    # NOTE: `filter_cond` must include query conditions to locate the element of array
    def update_sub_array_element(self, filter_cond, field, data):
        element = self.get_sub_array_element(filter_cond, field)
        element = dict(element, **data)
        result = self.update_one(filter_cond, {'$set': {'%s.$' % field: element}})
        return 1 == result.matched_count

    def delete_sub_array_element(self, filter_cond, field, data):
        result = self.update_one(filter_cond, {'$pull': {field: data}})
        return 1 == result.matched_count

    '''
       sub-array-element-field
    '''

    def get_sub_array_element_field(self):
        pass

    def add_sub_array_element_field(self):
        pass

    def update_sub_array_element_field(self):
        pass

    def delete_sub_array_element_field(self):
        pass

    '''
    def get_entrance_list(self, uid, aid, pid):
        result = []
        area = self.collection.find_one({'admin': uid, '_id': ObjectId(aid), 'partitions.pid': pid},
                                        {'partitions.$': 1})
        if area is not None and 'entrances' in area['partitions'][0]:
            for e in area['partitions'][0]['entrances']:
                out = {
                    'eid': e['eid'],
                    'name': e['name'],
                }
                result.append(out)
        return result

    def get_partition_list(self, uid, aid):
        result = []
        area = self.collection.find_one({'admin': uid, '_id': ObjectId(aid)},
                                        {'partitions.pid': 1,
                                         'partitions.type': 1,
                                         'partitions.name': 1,
                                         'partitions.number': 1})
        if area is not None and 'partitions' in area:
            result = area['partitions']
        return result
    '''


'''
    filter_cond = {'admin_id': uid, '_id': ObjectId(did)}
        modification = self.extract_fields(data, optional_fields=self.fields)
        result = self.collection.update_one(filter_cond, {'$set': modification})
        return 1 == result.modified_count

def create_partition(self, uid, aid, data):
        filter_cond = {'admin': uid, '_id': ObjectId(aid)}
        area = self.collection.find_one(filter_cond)
        if area is None:
            return {}
        partition = {
            'pid': str(ObjectId()),
            'type': data['type'],
            'name': data['name'],
        }
        # TODO: 保证 number 在 partitions 数组中的唯一性
        if partition['type'] == 'building':
            partition['number'] = data['number']
        self.collection.update_one(filter_cond, {'$push': {'partitions': partition}})
        return partition

    def update_partition(self, uid, aid, pid, data):
        filter_cond = {'admin': uid, '_id': ObjectId(aid), 'partitions.pid': pid}
        area = self.collection.find_one(filter_cond, {'partitions.$': 1})
        if area is None:
            return {}
        modification = {}
        if 'name' in data:
            modification['name'] = data['name']
        partition = area['partitions'][0]
        if partition['type'] == 'building' and 'number' in data:
            # TODO: 保证 number 在 partitions 数组中的唯一性
            modification['number'] = data['number']
        if len(modification) > 0:
            modification = dict(partition, **modification)
            self.collection.update_one(filter_cond, {'$set': {'partitions.$': modification}})
        return modification

    def delete_partition(self, uid, aid, pid):
        filter_cond = {'admin': uid, '_id': ObjectId(aid)}
        result = self.collection.update_one(filter_cond, {'$pull': {'partitions': {'pid': pid}}})
        # print result.raw_result
        # print result.modified_count
        return 1 == result.modified_count
        '''

if '__main__' == __name__:
    import pymongo

    db = pymongo.MongoClient().get_database('bora')
    c = db.get_collection('tests')
    c.drop()
    u0 = {'tag': 1, 'name': 'xq'}
    u1 = {'tag': 1, 'name': 'mj'}
    u2 = {'tag': 2, 'name': 'jx'}
    c.insert_many([u0, u1, u2])
    for a in c.aggregate([
        {'$group': {'_id': '$tag', 'names': {'$push': '$name'}}},
        {'$skip': 1},
        {'$limit': 1},
    ]):
        print a
